This
section demonstrates the terms and strategies discussed throughout this article. For each example, we show you the queries and their execution
plans. The goal of this section is not to show you how to read
execution plans, but to show you the performance difference displayed
within execution plans for the various indexing options. There are
entire books dedicated to nothing but indexing, so we won't show you
every indexing method example. However, this glimpse into the
performance improvements for the various types of indexing should
enable you to play with the different strategies within your
environment.
1. Setting Up the Scenario
For the demonstrations in this section, we use a series of sales tables within the Adventure-Works2008
database. We would like you to create copies of those tables, move data
into those copies, and create some indexes on them. We are going to use
three tables: the sales header table, sales details table, and the
product table. The following code sample creates the tables in a
separate schema and moves data into them:
USE AdventureWorks2008
GO
SELECT *
INTO AdventureWorks2008.APWRITER.Product
FROM AdventureWorks2008.Production.Product
SELECT *
INTO AdventureWorks2008.APWRITER.SalesOrderDetail
FROM AdventureWorks2008.Sales.SalesOrderDetail
SELECT *
INTO AdventureWorks2008.APWRITER.SalesOrderHeader
FROM AdventureWorks2008.Sales.SalesOrderHeader
If you do not have the APWRITER
schema created, then use a schema that exists on your system or create
a separate schema just for the tables. Before we begin, let's make sure
you understand the relationship between the tables. The
SalesOrderHeader table contains the information about when a sale was
created. The SalesOrderDetail table contains a list of all of the
details related to the sale. The Product table has the products that
exist within each sale detail. In other words, SalesOrderHeader tables
have order details and the order details have products. Enable the
Include Actual Execution Plan option before you execute the following
queries.
2. Table Scans
The first thing you notice
if you query the three newly created tables is that the query optimizer
does a table scan. As you can see, to retrieve all the data from a
table, the table scan is not necessarily a bad option; however,
retrieving specific records from within a heap table may prove costly.
Enable execution plans in your query window, and execute the following
queries. You'll see their execution plans displayed in Figure 1.
USE AdventureWorks2008
GO
SELECT *
FROM APWRITER.SalesOrderHeader
SELECT *
FROM APWRITER.SalesOrderDetail
SELECT *
FROM APWRITER.Product
Now instead of just
retrieving all the data from the tables, you may want to retrieve
specific results from each one of the tables. The following code
retrieves specific information from joining the newly created tables. Figure 2 shows the resulting execution plan.
SELECT soh.SalesOrderId, soh.OrderDate, soh.ShipDate,p.Name, sod.OrderQty,
sod.UnitPrice, sod.LineTotal
FROM apWriter.SalesOrderHeader soh JOIN apWriter.SalesOrderDetail sod
ON soh.SalesOrderId = sod.SalesOrderId
JOIN apWriter.Product p on sod.ProductId = p.ProductId
WHERE soh.SalesOrderId = 60941
The execution plan and
the response time suggest that joining multiple tables together via
table scan is not the most efficient way to retrieve data. So the first
rule that we would like to demonstrate is the benefit of creating
clustered indexes on tables.